
#include <wl_ohos.h>
#ifdef WL_EVENT
#include <bcmendian.h>
#include <dhd_config.h>

#define EVENT_ERROR(name, arg1, args...)                                       \
    do {                                                                       \
        if (ohos_msg_level & OHOS_ERROR_LEVEL) {                         \
            printk(KERN_ERR DHD_LOG_PREFIX "[%s] EVENT-ERROR) %s : " arg1,     \
                   name, __func__, ##args);                                    \
        }                                                                      \
    } while (0)
#define EVENT_TRACE(name, arg1, args...)                                       \
    do {                                                                       \
        if (ohos_msg_level & OHOS_TRACE_LEVEL) {                         \
            printk(KERN_INFO DHD_LOG_PREFIX "[%s] EVENT-TRACE) %s : " arg1,    \
                   name, __func__, ##args);                                    \
        }                                                                      \
    } while (0)
#define EVENT_DBG(name, arg1, args...)                                         \
    do {                                                                       \
        if (ohos_msg_level & OHOS_DBG_LEVEL) {                           \
            printk(KERN_INFO DHD_LOG_PREFIX "[%s] EVENT-DBG) %s : " arg1,      \
                   name, __func__, ##args);                                    \
        }                                                                      \
    } while (0)

#if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__) &&                       \
    (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
#define BCM_SET_LIST_FIRST_ENTRY(entry, ptr, type, member)                     \
    _Pragma("GCC diagnostic push")                                             \
        _Pragma("GCC diagnostic ignored \"-Wcast-qual\"")(entry) =             \
            list_first_entry((ptr), type, member);                             \
    _Pragma("GCC diagnostic pop")

#define BCM_SET_CONTAINER_OF(entry, ptr, type, member)                         \
    _Pragma("GCC diagnostic push")                                             \
        _Pragma("GCC diagnostic ignored \"-Wcast-qual\"") entry =              \
            container_of((ptr), type, member);                                 \
    _Pragma("GCC diagnostic pop")

#else
#define BCM_SET_LIST_FIRST_ENTRY(entry, ptr, type, member)                     \
    (entry) = list_first_entry((ptr), type, member);

#define BCM_SET_CONTAINER_OF(entry, ptr, type, member)                         \
    entry = container_of((ptr), type, member);

#endif /* STRICT_GCC_WARNINGS */

/* event queue for cfg80211 main event */
struct wl_event_q {
    struct list_head eq_list;
    u32 etype;
    wl_event_msg_t emsg;
    s8 edata[1];
};

typedef void (*EXT_EVENT_HANDLER)(struct net_device *dev, void *cb_argu,
                                  const wl_event_msg_t *e, void *data);

typedef struct event_handler_list {
    struct event_handler_list *next;
    struct net_device *dev;
    uint32 etype;
    EXT_EVENT_HANDLER cb_func;
    void *cb_argu;
    wl_event_prio_t prio;
} event_handler_list_t;

typedef struct event_handler_head {
    event_handler_list_t *evt_head;
} event_handler_head_t;

typedef struct wl_event_params {
    dhd_pub_t *pub;
    struct net_device *dev[DHD_MAX_IFS];
    struct event_handler_head evt_head;
    struct list_head eq_list; /* used for event queue */
    spinlock_t eq_lock;       /* for event queue synchronization */
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 0, 0))
    tsk_ctl_t thr_event_ctl;
#else
    struct workqueue_struct *event_workq; /* workqueue for event */
    struct work_struct event_work;        /* work item for event */
#endif
    struct mutex event_sync;
} wl_event_params_t;

static unsigned long wl_ext_event_lock_eq(struct wl_event_params *event_params)
{
    unsigned long flags;

    spin_lock_irqsave(&event_params->eq_lock, flags);
    return flags;
}

static void wl_ext_event_unlock_eq(struct wl_event_params *event_params,
                                   unsigned long flags)
{
    spin_unlock_irqrestore(&event_params->eq_lock, flags);
}

static void wl_ext_event_init_eq_lock(struct wl_event_params *event_params)
{
    spin_lock_init(&event_params->eq_lock);
}

static void wl_ext_event_init_eq(struct wl_event_params *event_params)
{
    wl_ext_event_init_eq_lock(event_params);
    INIT_LIST_HEAD(&event_params->eq_list);
}

static void wl_ext_event_flush_eq(struct wl_event_params *event_params)
{
    struct wl_event_q *e;
    unsigned long flags;

    flags = wl_ext_event_lock_eq(event_params);
    while (!list_empty_careful(&event_params->eq_list)) {
        BCM_SET_LIST_FIRST_ENTRY(e, &event_params->eq_list, struct wl_event_q,
                                 eq_list);
        list_del(&e->eq_list);
        kfree(e);
    }
    wl_ext_event_unlock_eq(event_params, flags);
}

/*
 * retrieve first queued event from head
 */

static struct wl_event_q *
wl_ext_event_deq_event(struct wl_event_params *event_params)
{
    struct wl_event_q *e = NULL;
    unsigned long flags;

    flags = wl_ext_event_lock_eq(event_params);
    if (likely(!list_empty(&event_params->eq_list))) {
        BCM_SET_LIST_FIRST_ENTRY(e, &event_params->eq_list, struct wl_event_q,
                                 eq_list);
        list_del(&e->eq_list);
    }
    wl_ext_event_unlock_eq(event_params, flags);

    return e;
}

/*
 * push event to tail of the queue
 */

static s32 wl_ext_event_enq_event(struct wl_event_params *event_params,
                                  u32 event, const wl_event_msg_t *msg,
                                  void *data)
{
    struct wl_event_q *e;
    s32 err = 0;
    uint32 evtq_size;
    uint32 data_len;
    unsigned long flags;
    gfp_t aflags;

    data_len = 0;
    if (data) {
        data_len = ntoh32(msg->datalen);
    }
    evtq_size = sizeof(struct wl_event_q) + data_len;
    aflags = (in_atomic()) ? GFP_ATOMIC : GFP_KERNEL;
    e = kzalloc(evtq_size, aflags);
    if (unlikely(!e)) {
        EVENT_ERROR("wlan", "event alloc failed\n");
        return -ENOMEM;
    }
    e->etype = event;
    memcpy(&e->emsg, msg, sizeof(wl_event_msg_t));
    if (data) {
        memcpy(e->edata, data, data_len);
    }
    flags = wl_ext_event_lock_eq(event_params);
    list_add_tail(&e->eq_list, &event_params->eq_list);
    wl_ext_event_unlock_eq(event_params, flags);

    return err;
}

static void wl_ext_event_put_event(struct wl_event_q *e)
{
    kfree(e);
}

#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 0, 0))
static int wl_ext_event_handler(void *data);
#define WL_EXT_EVENT_HANDLER() static int wl_ext_event_handler(void *data)
#else
static void wl_ext_event_handler(struct work_struct *data);
#define WL_EXT_EVENT_HANDLER()                                                 \
    static void wl_ext_event_handler(struct work_struct *data)
#endif

WL_EXT_EVENT_HANDLER()
{
    struct wl_event_params *event_params = NULL;
    struct wl_event_q *e;
    struct net_device *dev = NULL;
    struct event_handler_list *evt_node;
    dhd_pub_t *dhd;
    unsigned long flags = 0;
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 0, 0))
    tsk_ctl_t *tsk = (tsk_ctl_t *)data;
    event_params = (struct wl_event_params *)tsk->parent;
#else
    BCM_SET_CONTAINER_OF(event_params, data, struct wl_event_params,
                         event_work);
#endif

#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 0, 0))
    while (1) {
        if (down_interruptible(&tsk->sema) == 0) {
            SMP_RD_BARRIER_DEPENDS();
            if (tsk->terminated) {
                break;
            }
#endif
            DHD_EVENT_WAKE_LOCK(event_params->pub);
            while ((e = wl_ext_event_deq_event(event_params))) {
                if (e->emsg.ifidx >= DHD_MAX_IFS) {
                    EVENT_ERROR("wlan", "ifidx=%d not in range\n",
                                e->emsg.ifidx);
                    goto fail;
                }
                dev = event_params->dev[e->emsg.ifidx];
                if (!dev) {
                    EVENT_DBG("wlan", "ifidx=%d dev not ready\n",
                              e->emsg.ifidx);
                    goto fail;
                }
                dhd = dhd_get_pub(dev);
                if (e->etype > WLC_E_LAST) {
                    EVENT_TRACE(dev->name, "Unknown Event (%d): ignoring\n",
                                e->etype);
                    goto fail;
                }
                DHD_GENERAL_LOCK(dhd, flags);
                if (DHD_BUS_CHECK_DOWN_OR_DOWN_IN_PROGRESS(dhd)) {
                    EVENT_ERROR(dev->name, "BUS is DOWN.\n");
                    DHD_GENERAL_UNLOCK(dhd, flags);
                    goto fail;
                }
                DHD_GENERAL_UNLOCK(dhd, flags);
                EVENT_DBG(dev->name, "event type (%d)\n", e->etype);
                mutex_lock(&event_params->event_sync);
                evt_node = event_params->evt_head.evt_head;
                for (; evt_node;) {
                    if (evt_node->dev == dev &&
                        (evt_node->etype == e->etype ||
                         evt_node->etype == WLC_E_LAST)) {
                        evt_node->cb_func(dev, evt_node->cb_argu, &e->emsg,
                                          e->edata);
                    }
                    evt_node = evt_node->next;
                }
                mutex_unlock(&event_params->event_sync);
            fail:
                wl_ext_event_put_event(e);
            }
            DHD_EVENT_WAKE_UNLOCK(event_params->pub);
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 0, 0))
        } else {
            break;
        }
    }
    complete_and_exit(&tsk->completed, 0);
#endif
}

void wl_ext_event_send(void *params, const wl_event_msg_t *e, void *data)
{
    struct wl_event_params *event_params = params;
    u32 event_type = ntoh32(e->event_type);

    if (event_params == NULL) {
        EVENT_ERROR("wlan", "Stale event %d(%s) ignored\n", event_type,
                    bcmevent_get_name(event_type));
        return;
    }

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 0, 0))
    if (event_params->event_workq == NULL) {
        EVENT_ERROR("wlan", "Event handler is not created %d(%s)\n", event_type,
                    bcmevent_get_name(event_type));
        return;
    }
#endif

    if (likely(!wl_ext_event_enq_event(event_params, event_type, e, data))) {
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 0, 0))
        if (event_params->thr_event_ctl.thr_pid >= 0) {
            up(&event_params->thr_event_ctl.sema);
        }
#else
        queue_work(event_params->event_workq, &event_params->event_work);
#endif
    }
}

static s32 wl_ext_event_create_handler(struct wl_event_params *event_params)
{
    int ret = 0;
    EVENT_TRACE("wlan", "Enter\n");

#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 0, 0))
    PROC_START(wl_ext_event_handler, event_params, &event_params->thr_event_ctl,
               0, "ext_eventd");
    if (event_params->thr_event_ctl.thr_pid < 0) {
        ret = -ENOMEM;
    }
#else
    /* Allocate workqueue for event */
    if (!event_params->event_workq) {
        event_params->event_workq =
            alloc_workqueue("ext_eventd", WQ_MEM_RECLAIM | WQ_HIGHPRI, 0);
    }

    if (!event_params->event_workq) {
        EVENT_ERROR("wlan", "event_workq alloc_workqueue failed\n");
        ret = -ENOMEM;
    } else {
        INIT_WORK(&event_params->event_work, wl_ext_event_handler);
    }
#endif

    return ret;
}

static void wl_ext_event_free(struct wl_event_params *event_params)
{
    struct event_handler_list *node, *cur, **evt_head;

    evt_head = &event_params->evt_head.evt_head;
    node = *evt_head;

    for (; node;) {
        EVENT_TRACE(node->dev->name, "Free etype=%d\n", node->etype);
        cur = node;
        node = cur->next;
        kfree(cur);
    }
    *evt_head = NULL;
}

static void wl_ext_event_destroy_handler(struct wl_event_params *event_params)
{
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 0, 0))
    if (event_params->thr_event_ctl.thr_pid >= 0) {
        PROC_STOP(&event_params->thr_event_ctl);
    }
#else
    if (event_params && event_params->event_workq) {
        cancel_work_sync(&event_params->event_work);
        destroy_workqueue(event_params->event_workq);
        event_params->event_workq = NULL;
    }
#endif
}

int wl_ext_event_register(struct net_device *dev, dhd_pub_t *dhd, uint32 event,
                          void *cb_func, void *data, wl_event_prio_t prio)
{
    struct wl_event_params *event_params = dhd->event_params;
    struct event_handler_list *node, *leaf, *node_prev, **evt_head;
    int ret = 0;

    if (event_params) {
        mutex_lock(&event_params->event_sync);
        evt_head = &event_params->evt_head.evt_head;
        node = *evt_head;
        for (; node;) {
            if (node->dev == dev && node->etype == event &&
                node->cb_func == cb_func) {
                EVENT_TRACE(dev->name, "skip event %d\n", event);
                mutex_unlock(&event_params->event_sync);
                return 0;
            }
            node = node->next;
        }
        leaf = kmalloc(sizeof(event_handler_list_t), GFP_KERNEL);
        if (!leaf) {
            EVENT_ERROR(dev->name, "Memory alloc failure %d for event %d\n",
                        (int)sizeof(event_handler_list_t), event);
            mutex_unlock(&event_params->event_sync);
            return -ENOMEM;
        }
        leaf->next = NULL;
        leaf->dev = dev;
        leaf->etype = event;
        leaf->cb_func = cb_func;
        leaf->cb_argu = data;
        leaf->prio = prio;
        if (*evt_head == NULL) {
            *evt_head = leaf;
        } else {
            node = *evt_head;
            node_prev = NULL;
            for (; node;) {
                if (node->prio <= prio) {
                    leaf->next = node;
                    if (node_prev) {
                        node_prev->next = leaf;
                    } else {
                        *evt_head = leaf;
                    }
                    break;
                } else if (node->next == NULL) {
                    node->next = leaf;
                    break;
                }
                node_prev = node;
                node = node->next;
            }
        }
        EVENT_TRACE(dev->name, "event %d registered\n", event);
        mutex_unlock(&event_params->event_sync);
    } else {
        EVENT_ERROR(dev->name, "event_params not ready %d\n", event);
        ret = -ENODEV;
    }

    return ret;
}

void wl_ext_event_deregister(struct net_device *dev, dhd_pub_t *dhd,
                             uint32 event, void *cb_func)
{
    struct wl_event_params *event_params = dhd->event_params;
    struct event_handler_list *node, *prev, **evt_head;
    int tmp = 0;

    if (event_params) {
        mutex_lock(&event_params->event_sync);
        evt_head = &event_params->evt_head.evt_head;
        node = *evt_head;
        prev = node;
        for (; node;) {
            if (node->dev == dev && node->etype == event &&
                node->cb_func == cb_func) {
                if (node == *evt_head) {
                    tmp = 1;
                    *evt_head = node->next;
                } else {
                    tmp = 0;
                    prev->next = node->next;
                }
                EVENT_TRACE(dev->name, "event %d deregistered\n", event);
                kfree(node);
                if (tmp == 1) {
                    node = *evt_head;
                    prev = node;
                } else {
                    node = prev->next;
                }
                continue;
            }
            prev = node;
            node = node->next;
        }
        mutex_unlock(&event_params->event_sync);
    } else {
        EVENT_ERROR(dev->name, "event_params not ready %d\n", event);
    }
}

static s32 wl_ext_event_init_priv(struct wl_event_params *event_params)
{
    s32 err = 0;

    mutex_init(&event_params->event_sync);
    wl_ext_event_init_eq(event_params);
    if (wl_ext_event_create_handler(event_params)) {
        return -ENOMEM;
    }

    return err;
}

static void wl_ext_event_deinit_priv(struct wl_event_params *event_params)
{
    wl_ext_event_destroy_handler(event_params);
    wl_ext_event_flush_eq(event_params);
    wl_ext_event_free(event_params);
}

int wl_ext_event_attach_netdev(struct net_device *net, int ifidx, uint8 bssidx)
{
    struct dhd_pub *dhd = dhd_get_pub(net);
    struct wl_event_params *event_params = dhd->event_params;

    if (event_params && ifidx < DHD_MAX_IFS) {
        EVENT_TRACE(net->name, "ifidx=%d, bssidx=%d\n", ifidx, bssidx);
        event_params->dev[ifidx] = net;
    }

    return 0;
}

int wl_ext_event_dettach_netdev(struct net_device *net, int ifidx)
{
    struct dhd_pub *dhd = dhd_get_pub(net);
    struct wl_event_params *event_params = dhd->event_params;

    if (event_params && ifidx < DHD_MAX_IFS) {
        EVENT_TRACE(net->name, "ifidx=%d\n", ifidx);
        event_params->dev[ifidx] = NULL;
    }

    return 0;
}

s32 wl_ext_event_attach(struct net_device *net)
{
    struct dhd_pub *dhdp = dhd_get_pub(net);
    struct wl_event_params *event_params = NULL;
    s32 err = 0;

    event_params = kmalloc(sizeof(wl_event_params_t), GFP_KERNEL);
    if (!event_params) {
        EVENT_ERROR(net->name, "Failed to allocate memory (%zu)\n",
                    sizeof(wl_event_params_t));
        return -ENOMEM;
    }
    dhdp->event_params = event_params;
    memset(event_params, 0, sizeof(wl_event_params_t));
    event_params->pub = dhdp;

    err = wl_ext_event_init_priv(event_params);
    if (err) {
        EVENT_ERROR(net->name, "Failed to wl_ext_event_init_priv (%d)\n", err);
        goto ext_attach_out;
    }

    return err;
ext_attach_out:
    wl_ext_event_dettach(dhdp);
    return err;
}

void wl_ext_event_dettach(dhd_pub_t *dhdp)
{
    struct wl_event_params *event_params = dhdp->event_params;

    if (event_params) {
        wl_ext_event_deinit_priv(event_params);
        kfree(event_params);
        dhdp->event_params = NULL;
    }
}
#endif
